/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package pnt.server;

import java.io.*;
import java.net.*;
import java.util.logging.*;
import pnt.*;
import pnt.PNTMessage.Attribute;
import pnt.server.Registry.Node;
import pnt.server.Registry.Node.Instance;

/**
 *
 * @author andrew
 */
public class TCPConnection extends Connection {
    
    private static Logger logger = Logger.getLogger("pnt.server"); 
    
    private Socket primaryConnection;
    BufferedReader in;
    BufferedWriter out;
    
    // For garbage collection purposes
    private boolean terminated = false;
    
    public TCPConnection(Socket connection) throws IOException{
        
        this.primaryConnection = connection;
        
        InputStream sin = primaryConnection.getInputStream();
        OutputStream sout = primaryConnection.getOutputStream();

        InputStreamReader inr = new InputStreamReader(sin);
        OutputStreamWriter outw = new OutputStreamWriter(sout);

        in = new BufferedReader(inr);
        out = new BufferedWriter(outw);
        
    }

    public int attentionNeeded() {
        if (terminated)
            return -1;
        try {
            if (in.ready()) {
                return 1;
            }
            return 0;
        } catch (IOException ex) {
            System.err.println(ex);
            return 0;
        }
    }

    public void run() {
        
        if (attentionNeeded() == 1) try {
            
            PNTMessage m = PNTMessage.getMessage(primaryConnection);
            logger.log(Level.FINE, "Message received from " + primaryConnection.getInetAddress().getHostAddress() + ":" + primaryConnection.getPort(), m);
            PNTMessage r = handleReceivedMessage(m);
            if (r != null){
                out.write(r.toString());
                out.flush();
                logger.log(Level.FINE,"Message sent to " + primaryConnection.getInetAddress().getHostAddress() + ":" + primaryConnection.getPort(), r);
            }
        } catch (IOException ex) {
            System.err.println(ex);
        } catch (PNTMessage.MalformedPacketException ex){
            System.err.println("Malformed Packet Caught: " + ex);
        }
        
    }
    
    public boolean close() {
        
        // Close the protocol, and run one last time to send terminal message
        run();
        
        terminated = true;
        
        try {
            primaryConnection.close();
            return true;
        } catch (IOException ex) {
           return false;
        }
    }

    public InetAddress getClientAddress() {
        return primaryConnection.getInetAddress();
    }

    public int getClientPort() {
        return primaryConnection.getPort();
    }



    private PNTMessage handleReceivedMessage(PNTMessage m) {
        if (m.isRequest()){
            if (m.request == null)
                throw new Error ("request should not be null");
            switch (m.request){
                case ALLOCATEHEP:
                    
                    logger.log(Level.INFO, "Procesing ALLOCATE_HEP", m);
                    return generateAllocateHepReply(m);
                    
                case DISCOVERNEP:
                    
                    logger.log(Level.INFO, "Procesing DISCOVER_NEP", m);
                    return generateDiscoverNepReply(m);
                    
                case REGISTER:
                    
                    logger.log(Level.INFO, "Procesing REGISTER", m);
                    return generateRegisterReply(m);
                    
                default:
                    logger.log(Level.SEVERE, "Unsupported request received.", m);
                    throw new UnsupportedOperationException("Not yet implemented");
            }
        }
        return null;
    }
    
    
    private PNTMessage generateRegisterReply(PNTMessage m) {
        Node n;
        PNTMessage reply = null;
        Node.Instance instance = new Instance();
        
        String name = m.getAttribute(Attribute.ENDUSER);
        String application = m.getAttribute(Attribute.APPLICATION);
        
        String privateendpoints = m.getAttribute(Attribute.PRIVATEENDPOINTS);
        String extendpoints = m.getAttribute(Attribute.EXTENDPOINTS);
        
        // TODO: Parse and generate multiple endpoints
        @SuppressWarnings("unused")
		String privateIP = privateendpoints.substring(1,
                                    privateendpoints.indexOf(':'));
        @SuppressWarnings("unused")
		String privatePort = privateendpoints.substring(
                                    privateendpoints.indexOf(':' + 1),
                                    privateendpoints.indexOf(')'));;
        
        
        @SuppressWarnings("unused")
		String publicIP = extendpoints.substring(1,
                                    privateendpoints.indexOf(':'));
        @SuppressWarnings("unused")
		String publicPort = extendpoints.substring(
                                    privateendpoints.indexOf(':' + 1),
                                    privateendpoints.indexOf(')'));;                                
                                    
                                    
        instance.number = Integer.parseInt(m.getAttribute(Attribute.INSTANCE));
        
        Node.Instance[] instances = new Instance[0];
        instances[0] = instance;
        
        
        n = new Registry.Node(name , application, instances);
        
        
        
        try {
            Registry.addNode(n);
        
            reply = new PNTMessage(PNTMessage.ResponseType.OK);
            reply.insertAttribute( Attribute.TRANSACTION, 
                                   m.getAttribute(Attribute.TRANSACTION));
        } catch (Exception e) {
            reply = new PNTMessage(PNTMessage.ResponseType.TARGET_USER_UNAVAILABLE);
            reply.insertAttribute( Attribute.TRANSACTION, 
                                   m.getAttribute(Attribute.TRANSACTION));
        }
        return reply;
    }
    
    private PNTMessage generateDiscoverNepReply(PNTMessage m) {
        PNTMessage reply = null;
        
        if ( m.hasAttribute(Attribute.TRANSACTION) &&
             m.hasAttribute(Attribute.FROM) ){
            reply = new PNTMessage(PNTMessage.ResponseType.OK);
            
            reply.insertAttribute( Attribute.TRANSACTION, 
                                   m.getAttribute(Attribute.TRANSACTION));
            
            reply.insertAttribute( Attribute.NATENDPOINT,
                                   "("+getClientAddress().getHostAddress()+","+getClientPort()+")");
        }
        
        return reply;
    }    
    
    private PNTMessage generateAllocateHepReply(PNTMessage m){
        
        PNTMessage reply = null;
        
        if ( m.hasAttribute(Attribute.TRANSACTION) &&
             m.hasAttribute(Attribute.FROM) ){
            reply = new PNTMessage(PNTMessage.ResponseType.OK);
            
            reply.insertAttribute( Attribute.TRANSACTION, 
                                   m.getAttribute(Attribute.TRANSACTION));
            
            reply.insertAttribute( Attribute.NATENDPOINT,
                                   "("+getClientAddress().getHostAddress()+","+getClientPort()+")");
            
            // get helpers
            int endpointsNeeded = m.hasAttribute(Attribute.HEPCOUNTREQUEST) ? Integer.parseInt(m.getAttribute(Attribute.HEPCOUNTREQUEST)) : 1;
            TCPConnectionListener[] helpers = Server.getServer().getTCPHelpers(endpointsNeeded);
            
            String helperList = "";
            for(TCPConnectionListener t : helpers){
                helperList += "(" + t.getAddr().getHostAddress() + "," + t.getPort() + "),";
            }
            
            // Trim tailing comma
            reply.insertAttribute(Attribute.HELPERENDPOINTS, helperList.substring(0, helperList.length() - 1));
        }
        
        
        return reply;
    }

}
